<template>
  <div v-loading="loading" class="process-viewer">
    <el-header style="border-bottom: 1px solid rgb(218 218 218); height: auto">
      <div class="header-div">
        <div>
          <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
            <el-button size="small" icon="Rank" @click="processReZoom()" />
          </el-tooltip>
          <el-tooltip effect="dark" content="放大" placement="bottom">
            <el-button size="small" icon="ZoomIn" @click="processZoomIn()" />
          </el-tooltip>
          <el-tooltip effect="dark" content="缩小" placement="bottom">
            <el-button size="small" icon="ZoomOut" @click="processZoomOut()" />
          </el-tooltip>
        </div>
        <div>
          <div class="tips-label">
            <div class="un-complete">未完成</div>
            <div class="in-progress">进行中</div>
            <div class="complete">已完成</div>
          </div>
        </div>
      </div>
    </el-header>
    <div class="flow-containers">
      <el-container class="bpmn-el-container" style="align-items: stretch">
        <el-main style="padding: 0">
          <div class="canvas" ref="bpmnCanvas"></div>
        </el-main>
      </el-container>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick } from 'vue'
import BpmnViewer from 'bpmn-js'
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll'
import type { ModuleDeclaration } from 'didi'
import { ElMessage } from 'element-plus'
import type Canvas from 'diagram-js/lib/core/Canvas'
import type { ModdleElement } from 'bpmn-moddle'
import EventBus from 'diagram-js/lib/core/EventBus'
import Overlays from 'diagram-js/lib/features/overlays/Overlays'
import { getBpmnModelView } from '@/api/flow/instance'
import type { TaskVo, InstanceVo } from '@/types/flow'
import { useDictOptions } from '@/utils/dict'

const loading = ref(false)
const bpmnCanvas = ref<HTMLElement>()
const bpmnViewer = ref<BpmnViewer>()

// 加载任务状态字典
const { options: taskStatusOptions } = useDictOptions('task_status')

// 获取状态标签
const getStatusLabel = (status: string) => {
  const option = taskStatusOptions.value.find((opt) => opt.value === status)
  return option?.label || status
}

const init = (id: string) => {
  loading.value = true
  nextTick(async () => {
    if (bpmnViewer.value) bpmnViewer.value.destroy()
    bpmnViewer.value = new BpmnViewer({
      container: bpmnCanvas.value,
      additionalModules: [
        {
          //禁止滚轮滚动
          zoomScroll: ['value', ''],
        },
        ZoomScrollModule,
        MoveCanvasModule,
      ] as ModuleDeclaration[],
    })
    //查询后端流程实例信息
    const res = await getBpmnModelView(id)
    bpmnViewer.value
      .importXML(res.data.bpmnXml)
      .then((result) => {
        const { warnings } = result
        console.log('success !', warnings)
        processReZoom()
        loading.value = false
        setProcessStatus(res.data)
        addEventBusListener()
      })
      .catch((err) => {
        const { warnings, message } = err
        console.log('something went wrong:', warnings, message)
        loading.value = false
        ElMessage.error('加载流程图失败')
      })
  })
}

const defaultZoom = ref(1) // 默认缩放比例

/** Zoom：恢复 */
const processReZoom = () => {
  defaultZoom.value = 1
  bpmnViewer.value?.get<Canvas>('canvas').zoom('fit-viewport', { x: 0, y: 0 })
}

/** Zoom：放大 */
const processZoomIn = (zoomStep = 0.1) => {
  const newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100
  if (newZoom > 4) {
    throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
  }
  defaultZoom.value = newZoom
  bpmnViewer.value?.get<Canvas>('canvas').zoom(defaultZoom.value)
}

/** Zoom：缩小 */
const processZoomOut = (zoomStep = 0.1) => {
  const newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100
  if (newZoom < 0.2) {
    throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
  }
  defaultZoom.value = newZoom
  bpmnViewer.value?.get<Canvas>('canvas').zoom(defaultZoom.value)
}

const processInstance = ref<InstanceVo>() // 流程实例
const tasks = ref<TaskVo[]>([]) // 流程任务

/** 高亮流程 */
const setProcessStatus = (view: any) => {
  // 设置相关变量
  if (!view || !view.processInstance) {
    return
  }
  processInstance.value = view.processInstance
  tasks.value = view.tasks
  if (loading.value || !bpmnViewer.value) {
    return
  }
  const { unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds } =
    view
  const canvas = bpmnViewer.value.get<Canvas>('canvas')
  // 已完成节点
  if (Array.isArray(finishedSequenceFlowActivityIds)) {
    finishedSequenceFlowActivityIds.forEach((item: string) => canvas.addMarker(item, 'highlight'))
  }
  if (Array.isArray(finishedTaskActivityIds)) {
    finishedTaskActivityIds.forEach((item: string) => canvas.addMarker(item, 'highlight'))
  }
  // 未完成节点
  if (Array.isArray(unfinishedTaskActivityIds)) {
    unfinishedTaskActivityIds.forEach((item: string) => canvas.addMarker(item, 'highlight-todo'))
  }
}

const addEventBusListener = () => {
  const eventBus = bpmnViewer.value?.get<EventBus>('eventBus')
  const overlays = bpmnViewer.value?.get<Overlays>('overlays')
  eventBus?.on<ModdleElement>('element.hover', (e) => {
    const data = tasks.value.find((t) => t.taskDefinitionKey === e.element.id)
    if (e.element.type === 'bpmn:UserTask' && data) {
      setTimeout(() => {
        genNodeDetailBox(e, overlays, data)
      }, 10)
    }
  })
  eventBus?.on('element.out', () => {
    overlays?.clear()
  })
}
const genNodeDetailBox = (e: any, overlays: Overlays, data: TaskVo) => {
  overlays.add(e.element.id, {
    position: { top: e.element.height, left: 0 },
    html: `<div class="verlays">
                    <p>审批人员: ${data.assignee || ''}<p/>
                    <p>节点状态：${getStatusLabel(data.status)}</p>
                    ${data.reason ? `<p>审批意见：${data.reason}</p>` : ''}
                    <p>开始时间：${data.createTime || ''}</p>
                    <p>结束时间：${data.endTime || ''}</p>
                    <p>审批耗时：${data.durationInMillis || ''}</p>
                   </div>`,
  })
}

defineExpose({
  init,
})
</script>

<style lang="scss" scoped>
.header-div {
  display: flex;
  padding: 10px 0;
  justify-content: space-between;

  .tips-label {
    display: flex;
    div {
      margin-right: 10px;
      padding: 5px;
      font-size: 12px;
    }
    .un-complete {
      border: 1px solid #000;
    }
    .in-progress {
      background-color: rgb(255, 237, 204);
      border: 1px dashed orange;
    }
    .complete {
      background-color: rgb(204, 230, 204);
      border: 1px solid green;
    }
  }
}

.bpmn-el-container {
  height: calc(100vh - 350px);
}
.flow-containers {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  background: url('')
    repeat !important;

  .canvas {
    width: 100%;
    height: 100%;
  }
  .load {
    margin-right: 10px;
  }
  :deep(.el-form-item__label) {
    font-size: 13px;
  }

  :deep(.djs-palette) {
    left: 0 !important;
    top: 0;
    border-top: none;
  }

  :deep(.djs-container svg) {
    min-height: 650px;
  }

  :deep(.startEvent.djs-shape .djs-visual > :nth-child(1)) {
    fill: #77df6d !important;
  }
  :deep(.endEvent.djs-shape .djs-visual > :nth-child(1)) {
    fill: #ee7b77 !important;
  }
  :deep(.highlight.djs-shape .djs-visual > :nth-child(1)) {
    fill: green !important;
    stroke: green !important;
    fill-opacity: 0.2 !important;
  }
  :deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
    fill: green !important;
  }
  :deep(.highlight.djs-shape .djs-visual > path) {
    fill: green !important;
    fill-opacity: 0.2 !important;
    stroke: green !important;
  }
  :deep(.highlight.djs-connection > .djs-visual > path) {
    stroke: green !important;
  }

  // 边框滚动动画
  @keyframes path-animation {
    from {
      stroke-dashoffset: 100%;
    }

    to {
      stroke-dashoffset: 0%;
    }
  }

  :deep(.highlight-todo.djs-connection > .djs-visual > path) {
    animation: path-animation 60s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    stroke-dasharray: 4px !important;
    stroke: orange !important;
    fill-opacity: 0.2 !important;
    marker-end: url('#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr');
  }

  :deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
    animation: path-animation 60s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    stroke-dasharray: 4px !important;
    stroke: orange !important;
    fill: orange !important;
    fill-opacity: 0.2 !important;
  }
}
:deep(.verlays) {
  width: 250px;
  background: rgb(102, 102, 102);
  border-radius: 4px;
  border: 1px solid #ebeef5;
  color: #fff;
  padding: 15px 10px;
  p {
    line-height: 28px;
    margin: 0;
    padding: 0;
  }
  cursor: pointer;
}
</style>
